package org.apache.ibatis.common.parsing;

import com.sun.org.apache.xerces.internal.impl.dv.util.ByteListImpl;
import org.apache.ibatis.config.builder.BuilderException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.*;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @Name XPathParser
 * @Description: XPath 包装类
 * 描述信息：该类作为 “javax.xml.xpath.XPath” 的包装类，用来解析并读取 XML 文件中的内容。
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-05-16 13:47:06
 **/
public class XPathParser {
    // 是否开启验证
    private boolean validation;
    // 声明寻找 DTD 文件的方法，例如通过本地寻找、网络下载等
    private EntityResolver entityResolver;
    /**
     * MyBatis 配置文件中的 properties 信息
     * 说明：该信息会在解析配置文件的最开始就被解析，并在解析后续节点时发挥作用。
     * 具体如何发挥作用请参考 XPathParser.evalString(Object, String) 方法。
     */
    private Properties variables;
    // javax.xml.xpath.XPath 解析工具
    private XPath xPath;
    // 解析的整个 XML 文档
    private final Document document;

    public XPathParser(String xml) {
        // 初始化通用属性
        commonContructor(false, null, null);
        // 初始化 XML 文档
        this.document = createDocument(new InputSource(new StringReader(xml)));
    }

    public XPathParser(Reader reader) {
        commonContructor(false, null, null);
        this.document = createDocument(new InputSource(reader));
    }

    public XPathParser(InputStream inputStream) {
        commonContructor(false, null, null);
        this.document = createDocument(new InputSource(inputStream));
    }

    public XPathParser(Document document) {
        commonContructor(false, null, null);
        this.document = document;
    }

    public XPathParser(String xml, boolean validation) {
        commonContructor(validation, null, null);
        this.document = createDocument(new InputSource(new StringReader(xml)));
    }

    public XPathParser(Reader reader, boolean validation) {
        commonContructor(validation, null, null);
        this.document = createDocument(new InputSource(reader));
    }

    public XPathParser(InputStream inputStream, boolean validation) {
        commonContructor(validation, null, null);
        this.document = createDocument(new InputSource(inputStream));
    }

    public XPathParser(Document document, boolean validation) {
        commonContructor(validation, null, null);
        this.document = document;
    }

    public XPathParser(String xml, boolean validation, Properties variables) {
        commonContructor(validation, variables, null);
        this.document = createDocument(new InputSource(new StringReader(xml)));
    }

    public XPathParser(Reader reader, boolean validation, Properties variables) {
        commonContructor(validation, variables, null);
        this.document = createDocument(new InputSource(reader));
    }

    public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
        commonContructor(validation, variables, null);
        this.document = createDocument(new InputSource(inputStream));
    }

    public XPathParser(Document document, boolean validation, Properties variables) {
        commonContructor(validation, variables, null);
        this.document = document;
    }

    public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonContructor(validation, variables, entityResolver);
        this.document = createDocument(new InputSource(new StringReader(xml)));
    }

    public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonContructor(validation, variables, entityResolver);
        this.document = createDocument(new InputSource(reader));
    }

    public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonContructor(validation, variables, entityResolver);
        this.document = createDocument(new InputSource(inputStream));
    }

    public XPathParser(Document document, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonContructor(validation, variables, entityResolver);
        this.document = document;
    }

    private void commonContructor(boolean validation, Properties variables, EntityResolver entityResolver) {
        this.validation = validation;
        this.entityResolver = entityResolver;
        this.variables = variables;
        this.xPath = XPathFactory.newInstance().newXPath();
    }

    private Document createDocument(InputSource inputSource) {
        // 提示：该方法必须在通用属性初始化之后被调用。
        try {
            // Document 对象建造者工厂及其配置
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(validation);
            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);

            // Document 建造者及其配置
            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(entityResolver);
            builder.setErrorHandler(new ErrorHandler() {
                @Override
                public void warning(SAXParseException exception) throws SAXException {
                }

                @Override
                public void error(SAXParseException exception) throws SAXException {
                }

                @Override
                public void fatalError(SAXParseException exception) throws SAXException {
                }
            });
            // 创建 Document 对象
            return builder.parse(inputSource);
        } catch (Exception e) {
            throw new BuilderException("无法创建 Document 实例，原因：" + e, e);
        }
    }

    public void setVariables(Properties variables) {
        this.variables = variables;
    }

    /**
     * 解析 XML 文档中的字符串（默认根节点）
     *
     * @param expression 解析表达式
     * @return java.lang.String 结果
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-16 14:12:56
     */
    public String evalString(String expression) {
        return evalString(document, expression);
    }

    /**
     * 解析 XML 文档中的字符串
     *
     * @param root       指定根节点
     * @param expression 解析表达式
     * @return java.lang.String 结果
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-16 14:13:38
     */
    public String evalString(Object root, String expression) {
        // 解析结果
        String result = (String) evaluate(expression, root, XPathConstants.STRING);
        // 解析结果中的属性占位符
        result = PropertyParser.parse(result, variables);
        return result;
    }

    public Short evalShort(String expression) {
        return evalShort(document, expression);
    }

    public Short evalShort(Object root, String expression) {
        return Short.valueOf(evalString(root, expression));
    }

    public Integer evalInteger(String expression) {
        return evalInteger(document, expression);
    }

    public Integer evalInteger(Object root, String expression) {
        return Integer.valueOf(evalString(root, expression));
    }

    public Long evalLong(String expression) {
        return evalLong(document, expression);
    }

    public Long evalLong(Object root, String expression) {
        return Long.valueOf(evalString(root, expression));
    }

    public Float evalFloat(String expression) {
        return evalFloat(document, expression);
    }

    public Float evalFloat(Object root, String expression) {
        return Float.valueOf(evalString(root, expression));
    }

    public Double evalDouble(String expression) {
        return evalDouble(document, expression);
    }

    public Double evalDouble(Object root, String expression) {
        return (Double) evaluate(expression, root, XPathConstants.NUMBER);
    }

    public Boolean evalBoolean(String expression) {
        return evalBoolean(document, expression);
    }

    public Boolean evalBoolean(Object root, String expression) {
        return (Boolean) evaluate(expression, root, XPathConstants.BOOLEAN);
    }

    public XNode evalNode(String expression) {
        return evalNode(document, expression);
    }

    public XNode evalNode(Object root, String expression) {
        Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
        if (node == null) return null;
        return new XNode(this, variables, node);
    }

    public List<XNode> evalNodes(String expression) {
        return evalNodes(document, expression);
    }

    public List<XNode> evalNodes(Object root, String expression) {
        List<XNode> xNodes = new ArrayList<>();
        NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
        for (int i = 0; i < nodes.getLength(); i++) {
            xNodes.add(new XNode(this, variables, nodes.item(i)));
        }
        return xNodes;
    }

    /**
     * 解析 XML 节点的内容
     *
     * @param expression 解析表达式
     * @param root       指定根节点
     * @param returnType 节点内容类型
     * @return java.lang.Object 结果
     * @version v1.0.0
     * @author lichao
     * @date 2024-05-16 14:16:54
     */
    private Object evaluate(String expression, Object root, QName returnType) {
        try {
            return xPath.evaluate(expression, root, returnType);
        } catch (Exception e) {
            throw new BuilderException("无法解析 XPath，原因：" + e, e);
        }
    }

}

